﻿#nullable enable

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using PHMEE.CommonUtils;
using PHMEE.Localization;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Domain.Repositories;

namespace PHMEE.EnergyEfficiency;

public class LogAbstractAppService : PHMEEAppService, ILogAbstractAppService
{
    private readonly ILogAbstractRepository _logAbstractRepository;
    private readonly IPHMEETimeProvider _phmeeTimeProvider;
    private readonly IRepository<OvdPort, int> _ovdPortRepository;
    private readonly VesselEeConfigDomainService _vesselEeConfigDomainService;
    private readonly LogAbstractCommonAppService _logAbstractCommonAppService;
    private readonly FuelConsumptionCalculator _fuelConsumptionCalculator;
    private readonly DistanceCalculator _distanceCalculator;
    private readonly IGpsTdRepository _gpsTdRepository;
    private readonly ITimeSeriesDataLogRepository _timeSeriesDataLogRepository;

    // 是否是船端
    private readonly bool _isBoard;

    public LogAbstractAppService(ILogAbstractRepository logAbstractRepository,
        IPHMEETimeProvider phmeeTimeProvider,
        IRepository<OvdPort, int> ovdPortRepository,
        VesselEeConfigDomainService vesselEeConfigDomainService,
        LogAbstractCommonAppService logAbstractCommonAppService,
        FuelConsumptionCalculator fuelConsumptionCalculator,
        DistanceCalculator distanceCalculator,
        IGpsTdRepository gpsTdRepository,
        ITimeSeriesDataLogRepository timeSeriesDataLogRepository,
        IConfiguration configuration)
    {
        LocalizationResource = typeof(EnergyEfficiencyResource);
        
        _logAbstractRepository = logAbstractRepository;
        _phmeeTimeProvider = phmeeTimeProvider;
        _ovdPortRepository = ovdPortRepository;
        _vesselEeConfigDomainService = vesselEeConfigDomainService;
        _logAbstractCommonAppService = logAbstractCommonAppService;
        _fuelConsumptionCalculator = fuelConsumptionCalculator;
        _distanceCalculator = distanceCalculator;
        _gpsTdRepository = gpsTdRepository;
        _timeSeriesDataLogRepository = timeSeriesDataLogRepository;

        _isBoard = configuration.GetValue<bool>("IsBoard");
    }

    /// <inheritdoc />
    public async Task<List<FuelType>> GetFuelTypesAsync(Guid vesselId)
    {
        var vesselEeConfig = await _vesselEeConfigDomainService.GetOrDefaultByVesselIdAsync(vesselId);
        if (vesselEeConfig is null)
        {
            return [];
        }

        return vesselEeConfig.FuelTypes;
    }

    /// <inheritdoc />
    public async Task<CheckTsExistDto> CheckTsExistAsync(Guid vesselId, DateOnly dateUTC, TimeOnly timeUTC)
    {
        var logDateTimeUtc = DateTimeUtils.GetUtcDateTimeByDateOnlyTimeOnlyUpToMinute(dateUTC, timeUTC);

        // 检查同一时间戳是否已经存在 LogAbstract
        var existingLogAbstract = await _logAbstractRepository.GetOrDefaultAsync(vesselId, logDateTimeUtc);

        return existingLogAbstract is not null
            ? new CheckTsExistDto(true, L[PHMEEEnergyEfficiencyErrorCodes.ReportAlreadyExistsAtThisTs])
            : new CheckTsExistDto(false);
    }

    /// <inheritdoc />
    public async Task<LogAbstractDto> GetCreateLogAbstractDtoAsync(Guid vesselId, DateOnly dateUTC,
        TimeOnly timeUTC)
    {
        var dateTimeUtc = DateTimeUtils.GetUtcDateTimeByDateOnlyTimeOnlyUpToMinute(dateUTC, timeUTC);

        // var utcNow = _cbmTimeProvider.GetUtcNow();
        DateOnly dateUtcInputted = DateOnly.FromDateTime(dateTimeUtc);
        TimeOnly timeUtcInputted = TimeOnly.FromDateTime(dateTimeUtc);

        var vesselEeConfig = await _vesselEeConfigDomainService.GetOrDefaultByVesselIdAsync(vesselId);
        if (vesselEeConfig is null)
        {
            return new LogAbstractDto
            {
                DateUTC = dateUtcInputted,
                TimeUTC = timeUtcInputted,
            };
        }

        // 检查新 LogAbstract 的时间是否合法
        await ValidateNewLogAbstractDateTimeUtc(vesselId, dateTimeUtc);

        var dto = new LogAbstractDto
        {
            IMO = vesselEeConfig.IMO,
            Event = LogAbstractEventType.NoonPositionSeaPassage, // 默认设置为午报
            DateUTC = dateUtcInputted,
            TimeUTC = timeUtcInputted,
        };

        // 缺省值填入
        var latestLogAbstract = await _logAbstractRepository.GetLatestLogAbstractOrDefaultAsync(vesselId, dateTimeUtc);
        if (latestLogAbstract is not null)
        {
            // Common
            dto.VoyageFrom = latestLogAbstract.VoyageFrom;
            dto.VoyageTo = latestLogAbstract.VoyageTo;
            dto.VoyageNumber = latestLogAbstract.VoyageNumber;
            dto.TimeSincePreviousReport = (float)(dateTimeUtc - latestLogAbstract.Ts).TotalHours;

            // Cargo
            dto.CargoMt = latestLogAbstract.CargoMt;
            dto.CargoM3 = latestLogAbstract.CargoM3;
            dto.Passengers = latestLogAbstract.Passengers;
            dto.CargoCEU = latestLogAbstract.CargoCEU;
            dto.CargoTotalTEU = latestLogAbstract.CargoTotalTEU;
            dto.CargoTotalFullTEU = latestLogAbstract.CargoTotalFullTEU;
            dto.CargoFullReeferTEU = latestLogAbstract.CargoFullReeferTEU;
            dto.Reefer20Chilled = latestLogAbstract.Reefer20Chilled;
            dto.Reefer40Chilled = latestLogAbstract.Reefer40Chilled;
            dto.Reefer20Frozen = latestLogAbstract.Reefer20Frozen;
            dto.Reefer40Frozen = latestLogAbstract.Reefer40Frozen;

            // ROB
            dto.HFOROB = latestLogAbstract.HFOROB;
            dto.LFOROB = latestLogAbstract.LFOROB;
            dto.MGOROB = latestLogAbstract.MGOROB;
            dto.MDOROB = latestLogAbstract.MDOROB;
            dto.LPGPROB = latestLogAbstract.LPGPROB;
            dto.LPGBROB = latestLogAbstract.LPGBROB;
            dto.LNGROB = latestLogAbstract.LNGROB;
            dto.MethanolROB = latestLogAbstract.MethanolROB;
            dto.EthanolROB = latestLogAbstract.EthanolROB;
            dto.OROB = latestLogAbstract.OROB;
            dto.OROBType = latestLogAbstract.OROBType;

            // Machinery
            dto.ReeferSFOC = latestLogAbstract.ReeferSFOC;
            dto.ReeferFuelType = latestLogAbstract.ReeferFuelType;
            dto.CargoCoolingSFOC = latestLogAbstract.CargoCoolingSFOC;
            dto.CargoCoolingFuelType = latestLogAbstract.CargoCoolingFuelType;
            dto.DischargePumpSFOC = latestLogAbstract.DischargePumpSFOC;
            dto.DischargePumpFuelType = latestLogAbstract.DischargePumpFuelType;
        }

        // 如果系统不采集 GPS，则不填入 GPS 和航程
        if (vesselEeConfig.IsGpsAutoCollect)
        {
            // 填入 GPS 和航程
            await SetGPSValuesAsync(vesselId, vesselEeConfig, dateTimeUtc, dto, latestLogAbstract);
        }

        // 如果系统不采集流量计，则不填入油耗
        if (vesselEeConfig.IsFuelAutoCollect)
        {
            // 填入油耗
            await SetConsumptionCommonValuesAsync(vesselId, vesselEeConfig, latestLogAbstract, dateTimeUtc, dto);
        }
        
        // dto 所有 float 四舍五入保留到小数点后两位
        FloatRoundHelper.RoundProperties(dto);

        return dto;
    }

    /// <summary>
    /// 检查新 LogAbstract 的时间是否合法
    /// </summary>
    /// <param name="vesselId"></param>
    /// <param name="dateTimeUTC"></param>
    /// <exception cref="UserFriendlyException"></exception>
    private async Task ValidateNewLogAbstractDateTimeUtc(Guid vesselId, DateTime dateTimeUTC)
    {
        // 获取最后的一条 LogAbstract
        var lastLogAbstract = await _logAbstractRepository.GetLastLogAbstractOrDefaultAsync(vesselId);
        // 如果存在最后的一条 LogAbstract
        if (lastLogAbstract is not null)
        {
            // 检查新 LA 时间是否在最后一条 LA 时间之后
            if (dateTimeUTC < lastLogAbstract.Ts)
            {
                // 无法为比最新日志日期更早的日期录入数据。请确认日期，并确保按照时间顺序录入数据
                throw new UserFriendlyException(L[PHMEEEnergyEfficiencyErrorCodes.EarlierDateEntryNotAllowed],
                    PHMEEEnergyEfficiencyErrorCodes.EarlierDateEntryNotAllowed);
            }
            
            // 检查是否存在日期间隔
            var daysDifference = (dateTimeUTC.Date - lastLogAbstract.Ts.Date).Days;
            if (daysDifference > 1)
            {
                // 存在未录入的日期。自最后一次录入以来每一天都需要录入数据，请依次补录缺失的日期。
                throw new UserFriendlyException(L[PHMEEEnergyEfficiencyErrorCodes.SequentialDateEntryRequired],
                    PHMEEEnergyEfficiencyErrorCodes.SequentialDateEntryRequired);
            }
        }
    }

    /// <inheritdoc />
    public async Task CreateAsync(Guid vesselId, LogAbstractDto input)
    {
        var vesselEeConfig = await _vesselEeConfigDomainService.GetOrDefaultByVesselIdAsync(vesselId);
        if (vesselEeConfig is null)
        {
            throw new UserFriendlyException(L[PHMEEEnergyEfficiencyErrorCodes.VesselConfigNotFound],
                PHMEEEnergyEfficiencyErrorCodes.VesselConfigNotFound);
        }
        
        var logDateTimeUtc = DateTimeUtils.GetUtcDateTimeByDateOnlyTimeOnlyUpToMinute(input.DateUTC, input.TimeUTC);
        
        // 检查新 LogAbstract 的时间是否合法
        await ValidateNewLogAbstractDateTimeUtc(vesselId, logDateTimeUtc);
        
        // input 所有 float 四舍五入保留到小数点后两位
        FloatRoundHelper.RoundProperties(input);
        
        var logAbstract = _logAbstractCommonAppService.BuildLogAbstract(vesselId, input);
        
        await _logAbstractRepository.InsertAsync(logAbstract, vesselId);

        // 如果是船端，则插入 TimeSeriesDataLog 数据同步日志
        if (_isBoard)
        {
            // 插入 TimeSeriesDataLog 数据同步日志
            await _timeSeriesDataLogRepository.InsertAsync(new TimeSeriesDataLog(GuidGenerator.Create(), vesselId)
            {
                Ts = logAbstract.Ts,
                SyncDataType = SyncDataTypeUtils.GetSyncDataType(typeof(LogAbstract)),
                OperationType = OperationType.Insert,
                OperationTime = DateTime.UtcNow
            });
        }
    }

    /// <inheritdoc />
    public async Task DeleteAsync(Guid vesselId, DateTime ts)
    {
        ts = ts.ToUtcKind();
        
        // 获取最后的一条 LogAbstract
        var lastLogAbstract = await _logAbstractRepository.GetLastLogAbstractOrDefaultAsync(vesselId);
        if (lastLogAbstract is not null && lastLogAbstract.Ts != ts)
        {
            // 只允许删除最新的 LogAbstract
            throw new UserFriendlyException(L[PHMEEEnergyEfficiencyErrorCodes.DeleteOnlyMostRecentLogAbstract],
                PHMEEEnergyEfficiencyErrorCodes.DeleteOnlyMostRecentLogAbstract);
        }
        
        await _logAbstractRepository.DeleteAsync(vesselId, ts);

        // 如果是船端，则插入数据同步日志
        if (_isBoard)
        {
            // 插入数据同步日志
            await _timeSeriesDataLogRepository.InsertAsync(new TimeSeriesDataLog(GuidGenerator.Create(), vesselId)
            {
                Ts = ts,
                SyncDataType = SyncDataTypeUtils.GetSyncDataType(typeof(LogAbstract)),
                OperationType = OperationType.Delete,
                OperationTime = DateTime.UtcNow
            });
        }
    }

    /// <inheritdoc />
    public async Task UpdateAsync(Guid vesselId, LogAbstractDto input)
    {
        var vesselEeConfig = await _vesselEeConfigDomainService.GetOrDefaultByVesselIdAsync(vesselId);
        if (vesselEeConfig is null)
        {
            throw new UserFriendlyException(L[PHMEEEnergyEfficiencyErrorCodes.VesselConfigNotFound],
                PHMEEEnergyEfficiencyErrorCodes.VesselConfigNotFound);
        }

        // 不知道为什么，从 Dto 中获取到的时间是本地时间，所以这里强制转换为 UTC 时间
        input.Ts = input.Ts.ToUniversalTime();
        
        // 根据 input.Ts 获取原 LogAbstract
        var originalLogAbstract = await _logAbstractRepository.GetOrDefaultAsync(vesselId, input.Ts);
        if (originalLogAbstract is null)
        {
            throw new UserFriendlyException(L[PHMEEEnergyEfficiencyErrorCodes.LogAbstractNotFound],
                PHMEEEnergyEfficiencyErrorCodes.LogAbstractNotFound);
        }
        
        // 填表时间的 7 日内可修改，超出 7 日无法修改
        var originalCreationTime = originalLogAbstract.CreationTime;
        var utcNow = _phmeeTimeProvider.GetUtcNow();
        if ((utcNow - originalCreationTime).TotalDays > 7)
        {
            throw new UserFriendlyException(L[PHMEEEnergyEfficiencyErrorCodes.CannotModifyReportBeforeSevenDays],
                PHMEEEnergyEfficiencyErrorCodes.CannotModifyReportBeforeSevenDays);
        }
        
        // input 所有 float 四舍五入保留到小数点后两位
        FloatRoundHelper.RoundProperties(input);

        // 构建新的 LogAbstract
        var logAbstract = _logAbstractCommonAppService.BuildLogAbstract(vesselId, input);
        // 更新时，CreationTimeUTC 保持不变
        logAbstract.CreationTime = originalCreationTime;

        // 保存新的 LogAbstract
        await _logAbstractRepository.InsertAsync(logAbstract, vesselId);
        
        // 如果是船端，则插入数据同步日志
        if (_isBoard)
        {
            // 插入 TimeSeriesDataLog 数据同步日志
            await _timeSeriesDataLogRepository.InsertAsync(new TimeSeriesDataLog(GuidGenerator.Create(), vesselId)
            {
                Ts = logAbstract.Ts,
                SyncDataType = SyncDataTypeUtils.GetSyncDataType(typeof(LogAbstract)),
                OperationType = OperationType.Insert, // 时序数据库不允许 Update，所以也是 Insert
                OperationTime = DateTime.UtcNow
            });
        }
    }

    /// <inheritdoc />
    public async Task<PagedResultDto<LogAbstractDto>> GetListAsync(Guid vesselId,
        PagedAndSortedWithTimeRangeResultRequestDto input)
    {
        var (startDateTime, endDateTime) = QueryDateTimeUtils.ProcessForDateRangeQuery(input.StartTime, input.EndTime);

        var totalCount = await _logAbstractRepository.CountAsync(vesselId, startDateTime, endDateTime);
        var logAbstracts = await _logAbstractRepository.GetListDescAsync(vesselId, startDateTime, endDateTime,
            input.SkipCount, input.MaxResultCount);
        var resultDtos = ObjectMapper.Map<List<LogAbstract>, List<LogAbstractDto>>(logAbstracts);
        var result = new PagedResultDto<LogAbstractDto>(totalCount, resultDtos);
        return result;
    }

    /// <inheritdoc />
    public async Task<List<OVDPortDto>> GetPortsAsync(string input)
    {
        var portDbSet = await _ovdPortRepository.GetQueryableAsync();
        var query = portDbSet.Where(p => p.Identifier.Contains(input));
        var ovdPorts = await AsyncExecuter.ToListAsync(query);
        var dtos = ObjectMapper.Map<List<OvdPort>, List<OVDPortDto>>(ovdPorts);

        return dtos;
    }
    
    /// <summary>
    /// 判断是否是采集版，如果是，则根据流量计加装的情况自动填入 LAConsumptionCommon 信息
    /// </summary>
    /// <param name="vesselId"></param>
    /// <param name="vesselEeConfig"></param>
    /// <param name="latestLogAbstract"></param>
    /// <param name="dateTimeUtc"></param>
    /// <param name="dto"></param>
    private async Task SetConsumptionCommonValuesAsync(Guid vesselId, VesselEeConfig vesselEeConfig,
        LogAbstract? latestLogAbstract, DateTime dateTimeUtc, LogAbstractDto dto)
    {
        // 上一条 LogAbstract 不存在时，说明刚刚装船，不应该填入油耗
        if (latestLogAbstract is not null)
        {
            var fuelConsumptionData = await GetFuelConsumptionDataAsync(vesselId, latestLogAbstract, dateTimeUtc);
            FillConsumptionCommonValues(fuelConsumptionData, dto);
        }
    }

    /// <summary>
    /// 获取燃料消耗数据
    /// </summary>
    /// <param name="vesselId"></param>
    /// <param name="latestLogAbstract"></param>
    /// <param name="dateTimeUtc"></param>
    /// <returns></returns>
    private async Task<FuelConsumptionData> GetFuelConsumptionDataAsync(Guid vesselId,
        LogAbstract? latestLogAbstract, DateTime dateTimeUtc)
    {
        DateTime queryStartTimeUtc = default;
        if (latestLogAbstract is not null)
        {
            queryStartTimeUtc = latestLogAbstract.Ts;
        }
        
        var (meConsumptions, aeConsumptions, boConsumptions, totalConsumptions) = 
            await _fuelConsumptionCalculator.GetFuelConsumptionForDateTimePeriodGroupedByUnitAsync(vesselId, queryStartTimeUtc, dateTimeUtc);

        var fuelConsumptionData = new FuelConsumptionData
        {
            MEConsumptionDict = meConsumptions,
            AEConsumptionDict = aeConsumptions,
            BoilerConsumptionDict = boConsumptions
        };

        return fuelConsumptionData;
    }

    /// <summary>
    /// 填入燃料消耗数据到 DTO
    /// </summary>
    /// <param name="fuelConsumptionData"></param>
    /// <param name="dto"></param>
    private static void FillConsumptionCommonValues(FuelConsumptionData fuelConsumptionData, LogAbstractDto dto)
    {
        if (fuelConsumptionData.MEConsumptionDict.Count != 0)
        {
            dto.SetMEConsumptions(fuelConsumptionData.MEConsumptionDict);
        }

        if (fuelConsumptionData.AEConsumptionDict.Count != 0)
        {
            dto.SetAEConsumptions(fuelConsumptionData.AEConsumptionDict);
        }

        if (fuelConsumptionData.BoilerConsumptionDict.Count != 0)
        {
            dto.SetBoilerConsumptions(fuelConsumptionData.BoilerConsumptionDict);
        }
    }
    
    private class FuelConsumptionData
    {
        public Dictionary<FuelType, float> MEConsumptionDict { get; set; } = new();
        public Dictionary<FuelType, float> AEConsumptionDict { get; set; } = new();
        public Dictionary<FuelType, float> BoilerConsumptionDict { get; set; } = new();
    }

    /// <summary>
    /// 判断是否是采集版，如果是，则自动填入 GPS 信息和航程
    /// </summary>
    /// <param name="vesselId"></param>
    /// <param name="vesselEeConfig"></param>
    /// <param name="dateTimeUTC"></param>
    /// <param name="dto"></param>
    /// <param name="latestLogAbstract"></param>
    private async Task SetGPSValuesAsync(Guid vesselId, VesselEeConfig vesselEeConfig, DateTime dateTimeUTC,
        LogAbstractDto dto, LogAbstract? latestLogAbstract)
    {
        // 采集版自动填入 GPS 信息和航程
        bool gpsAutoCollect = await _gpsTdRepository.IsSubTableExistAsync(vesselId);
        if (gpsAutoCollect)
        {
            var tdQueryStartDateTimeUtc = dateTimeUTC.AddMinutes(-PHMEEConsts.DataCollectionIntervalMinutes);
            var tdQueryEndDateTimeUtc = dateTimeUTC;
            // 自动填入纬度和经度
            var gpsTde = await _gpsTdRepository.LatestOrDefaultAsync(vesselId, tdQueryStartDateTimeUtc, tdQueryEndDateTimeUtc);
            if (gpsTde is not null)
            {
                var (latitudeNorthSouth, latitudeDegree, latitudeMinutes) = SetLatitudeFromDecimal(gpsTde.Latitude);
                var (longitudeEastWest, longitudeDegree, longitudeMinutes) = SetLongitudeFromDecimal(gpsTde.Longitude);
                dto.LatitudeNorthSouth = latitudeNorthSouth;
                dto.LatitudeDegree = latitudeDegree;
                dto.LatitudeMinutes = latitudeMinutes;
                dto.LongitudeEastWest = longitudeEastWest;
                dto.LongitudeDegree = longitudeDegree;
                dto.LongitudeMinutes = longitudeMinutes;
            }

            // 自动填入航程
            if (latestLogAbstract is not null)
            {
                var distance = await _distanceCalculator.GetDistanceForDateTimePeriodAsync(vesselId, latestLogAbstract.Ts, dateTimeUTC);
                dto.Distance = distance;

                // // 如果存在上次的 LogAbstract，则取上次 LogAbstract 的计程仪对应的航程
                // var latestLogAbstractVessel =
                //     await _vesselRepository.GetLatestRecordOrDefaultAsync(vesselId, latestLogAbstract.Ts);
                // if (vessel is not null && latestLogAbstractVessel is not null)
                // {
                //     // 如果本次的 Vessel 和上次的 Vessel 都存在，则计算出航程
                //     dto.Distance = vessel.Distance - latestLogAbstractVessel.Distance;
                // }
            }
        }
    }
    
    // TODO: GPS float 和 度、分 的转换逻辑应该采用通用的，需调整

    /// <summary>
    /// 把 float 的纬度转换为度分
    /// </summary>
    /// <param name="latitude"></param>
    /// <returns></returns>
    private static (LatitudeNorthSouth northSouth, int latitudeDegree, float latitudeMinutes) SetLatitudeFromDecimal(
        float latitude)
    {
        // Determine the hemisphere and make the degrees positive for calculation
        LatitudeNorthSouth northSouth = latitude < 0 ? LatitudeNorthSouth.S : LatitudeNorthSouth.N;
        latitude = Math.Abs(latitude);

        // Separate degrees and minutes
        int latitudeDegree = (int)latitude;
        float latitudeMinutes = (latitude - latitudeDegree) * 60;

        return (northSouth, latitudeDegree, latitudeMinutes);
    }

    /// <summary>
    /// 把 float 的经度转换为度分
    /// </summary>
    /// <param name="longitude"></param>
    /// <returns></returns>
    private static (LongitudeEastWest eastWest, int longitudeDegree, float longitudeMinutes) SetLongitudeFromDecimal(
        float longitude)
    {
        // Determine the hemisphere and make the degrees positive for calculation
        LongitudeEastWest eastWest = longitude < 0 ? LongitudeEastWest.W : LongitudeEastWest.E;
        longitude = Math.Abs(longitude);

        // Separate degrees and minutes
        int longitudeDegree = (int)longitude;
        float longitudeMinutes = (longitude - longitudeDegree) * 60;

        return (eastWest, longitudeDegree, longitudeMinutes);
    }
}